-module(chat_server).
-export([start/1]).

-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

start(Port) ->
    Pid = spawn(fun() -> manage_clients([]) end),
    register(client_manager, Pid),
    {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
    do_accept(LSocket).

do_accept(LSocket) ->
    {ok, Socket} = gen_tcp:accept(LSocket),
    spawn(fun() -> handler_client(Socket) end),
    client_manager ! {connect, Socket},
    do_accept(LSocket).

handler_client(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
            client_manager ! {data, Data, Socket},
            handler_client(Socket);
        {error, closed} ->
            client_manager ! {disconnect, Socket}
    end.

manage_clients(Sockets) ->
    receive
        {connect, Socket} ->
            io:fwrite("Socket connected: ~w~n", [Socket]),
            NewSockets = [{Socket, 'NEW'} | Sockets];
        {disconnect, Socket} ->
            io:fwrite("Socket disconnected: ~w~n", [Socket]),
            NewSockets = lists:keydelete(Socket, 1, Sockets);
        {data, Data, Socket} ->
            NewSockets = handle_data(Sockets, Data, Socket)
    end,
    manage_clients(NewSockets).

handle_data(Sockets, Data, Socket) ->
    case binary_to_term(Data) of
        {login, Name} ->
            handle_login(Sockets, Name, Socket);            
        logout ->
            handle_logout(Sockets, Socket);
        who ->
            handle_who(Sockets, Socket);
        {say, To, Msg} ->
            handle_say(Sockets, Socket, To, Msg);
        {say2all, Msg} ->
            handle_say2all(Sockets, Socket, Msg)
    end.

handle_login(Sockets, Name, Socket) ->
    Sockets1 = lists:keydelete(Socket, 1, Sockets),
    case lists:keymember(Name, 2, Sockets1) of
        true ->
            send(Socket, {error, name_exist}),
            Sockets1;
        false ->
            send(Socket, {ok, login_success}),
            [{Socket, Name} | Sockets1]
    end.

handle_logout(Sockets, Socket) ->
    lists:keydelete(Socket, 1, Sockets).

handle_who(Sockets, Socket) ->
    Find = fun(Socket_Name) -> 
        {_Socket, Name} = Socket_Name,
        Name
    end,
    send(Socket, {ok, lists:map(Find, Sockets)}),
    Sockets.

handle_say(Sockets, Socket, To, Msg) ->
    {value, {Socket, FromName}} = lists:keysearch(Socket, 1, Sockets),
    case lists:keysearch(To, 2, Sockets) of
        false ->
            send(Socket, {ok, receiver_not_found});
        {value, {ToSocket, To}} when ToSocket /= Socket ->
            send(Socket, {ok, send_success}),
            send(ToSocket, {msg, FromName, Msg});
        _Same ->
            send(Socket, {ok, can_not_say_to_yourself})
    end,
    Sockets.

handle_say2all(Sockets, Socket, Msg) ->
    {value, {Socket, FromName}} = lists:keysearch(Socket, 1, Sockets),
    Say = fun(Socket_Name) ->
        {ToSocket, _To} = Socket_Name,
        if 
            ToSocket /= Socket ->
                send(ToSocket, {msg, FromName, Msg});
            true ->
                ok
        end
    end,
    lists:foreach(Say, Sockets),
    Sockets.
    
send(Socket, Msg) ->
    gen_tcp:send(Socket, term_to_binary(Msg)).
